﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using PriLib.Pers;

namespace PriLib
{
    public class PriFileReader
    {
        //"E:\\Projects\\Test.pri"
        //"E:\\Projects\\Windows.UI.Logon.Test.pri"
        public static PriRaw Read(string path)
        {
            int i32;
            long i64;
            uint ui32;
            ushort ui16;

            BinaryReader br = new BinaryReader(File.Open(path, FileMode.Open));
            PriRaw priRaw = new PriRaw();
            var chars = br.ReadChars(8);
            priRaw.Header.magic = chars.ToRealString();
            if (chars.ToRealString() != "mrm_pri2")
            {
                throw new FormatException("This is not a correct pri2 format.");
            }
            priRaw.Header.Version.major = br.ReadUInt16();
            priRaw.Header.Version.minor = br.ReadUInt16();

            priRaw.Header.totalSize = br.ReadUInt32();
            i32 = br.ReadInt32(); //Unknown, maybe platform?
            priRaw.Header.HeaderLength = br.ReadUInt32(); //header length
            //Always 0x00000020(32) 0x000000C0(192) ?
            priRaw.Header.tocNumEntries = br.ReadUInt16();
            priRaw.Header.descriptorIndex = br.ReadInt16();
            i32 = br.ReadInt32(); //Unknown
            for (int i = 0; i < priRaw.Header.tocNumEntries; i++)
            {
                var t = br.ReadChars(16).ToRealString().Trim();
                PriRaw.PriTocEntry toc = InstantiateTocEntry(t);
                toc.type = t;
                toc.index = i;
                toc.flags = br.ReadUInt32();
                toc.sectionFlags = br.ReadUInt32();
                toc.offset = br.ReadUInt32(); //offset
                toc.size = br.ReadUInt32();
                priRaw.Toc.Add(toc);
            }
            //For every toc, there will be a section
            for (int i = 0; i < priRaw.Header.tocNumEntries; i++)
            {
                var toc = priRaw.Toc[i];
                var strs = br.ReadChars(16).ToRealString().Trim(); //16 chars name
                if (toc.type != strs)
                {
                    Console.WriteLine("Toc is not corresponding.");
                }
                switch (toc.EntryType)
                {
                    case PriRaw.TocEntryType.Unknown:
                        toc.Data = br.ReadBytes((int)toc.size - 16);
                        break;
                    case PriRaw.TocEntryType.MrmDecnInfo:
                        toc.Data = br.ReadBytes((int)toc.size - 16);
                        break;
                    case PriRaw.TocEntryType.MrmPridescex:
                        toc.Data = br.ReadBytes((int)toc.size - 16);
                        break;
                    case PriRaw.TocEntryType.MrmHschemaex:
                        i64 = br.BaseStream.Position;
                        toc.Data = br.ReadBytes((int)toc.size - 16);
                        br.BaseStream.Seek(i64, SeekOrigin.Begin);
                        //Names of items
                        var hschemaexToc = (PriRaw.HschemaexEntry)toc;
                        //br.ReadBytes((int)toc.size - 16);
                        br.ReadInt32();//0
                        br.ReadInt32();//0
                        hschemaexToc.TocLength = br.ReadUInt32();
                        br.ReadInt32();//0

                        ui32 = br.ReadUInt32();//Unknown C:2490369 L:1835009
                        ui32 = br.ReadUInt32();//Unknown C:27 L:17
                        strs = br.ReadChars(16).ToRealString().Trim();//[def_hnamesx]  //不足位补空格，两个空格一个\0

                        priRaw.Schema.Version.major = br.ReadUInt16();
                        priRaw.Schema.Version.minor = br.ReadUInt16();
                        br.ReadInt32();//0
                        priRaw.Schema.checksum = br.ReadInt32();
                        priRaw.Schema.numScopes = br.ReadUInt32();//C:2 L:38
                        priRaw.Schema.numItems = br.ReadUInt32();//C:160 L:70
                        priRaw.Schema.uniqueId = br.ReadUnicodeString();
                        priRaw.Schema.simpleName = br.ReadUnicodeString();
                        //Here is a unknown part about 1770 in L and 2310 in C. What is that?
                        ui16 = br.ReadUInt16();
                        ui16 = br.ReadUInt16();
                        ui16 = br.ReadUInt16();
                        ui16 = br.ReadUInt16();
                        //br.BaseStream.Seek(2869, SeekOrigin.Begin);//C:2869 L:2289
                        //for (int j = 0; j < priRaw.Schema.numItems + priRaw.Schema.numScopes; j++)
                        //{
                        //    strs = br.ReadSigleByteString();
                        //    Console.WriteLine(strs);
                        //}
                        var p = br.BaseStream.Seek(toc.offset + toc.TocLength + priRaw.Header.HeaderLength,SeekOrigin.Begin);
                        break;

                    case PriRaw.TocEntryType.MrmResMap2:
                        toc.Data = br.ReadBytes((int)toc.size - 16);
                        break;

                    case PriRaw.TocEntryType.MrmDataItem:
                        var dataItemToc = (PriRaw.DataItemEntry)toc;
                        i32 = br.ReadInt32();//0
                        i32 = br.ReadInt32();//0
                        dataItemToc.TocLength = br.ReadUInt32();//Toc Length
                        i32 = br.ReadInt32();
                        i32 = br.ReadInt32();
                        dataItemToc.StringCount = br.ReadUInt16();
                        dataItemToc.BlobCount = br.ReadUInt16();
                        dataItemToc.DataLength = br.ReadUInt32();
                        ui16 = br.ReadUInt16();//Unknown
                        ui16 = br.ReadUInt16();
                        //br.ReadInt32();//Wrong?
                        //br.ReadInt32();
                        dataItemToc.DataAndContentLength = br.ReadUInt32();//DataLength+ContentLengthAligned
                        //for (int j = 0; j < dataItemToc.StringCount; j++) //Wrong?
                        //{
                        //    ui32 = br.ReadUInt32();
                        //}
                        dataItemToc.BlobTableOffset = br.BaseStream.Position;
                        for (int j = 0; j < dataItemToc.BlobCount; j++)
                        {
                            var blob = new PriRaw.PriBlob();
                            blob.BlobTablePosition = br.BaseStream.Position;
                            blob.Offset = br.ReadUInt32();
                            blob.Length = br.ReadUInt32();
                            dataItemToc.Blobs.Add(blob);
                        }
                        //dataItemToc.DataOffset = dataItemToc.BlobTableOffset + dataItemToc.BlobCount * 8;
                        //br.BaseStream.Seek(dataItemToc.DataOffset, SeekOrigin.Begin);
                        dataItemToc.DataOffset = br.BaseStream.Position;

                        var currentPos = br.BaseStream.Position;
                        for (int index = 0; index < dataItemToc.Blobs.Count; index++)
                        {
                            var priBlob = dataItemToc.Blobs[index];
                            if (priBlob.Length < priRaw.Header.totalSize && priBlob.Offset < priRaw.Header.totalSize)
                            {
                                br.BaseStream.Seek(dataItemToc.DataOffset + priBlob.Offset, SeekOrigin.Begin);
                                priBlob.Data = br.ReadBytes((int) priBlob.Length);
                                //if (PersReader.IsPers(priBlob.Data))
                                //{
                                //    var pers = PersReader.Read(priBlob.Data);
                                //    pers.Xml.WriteXmlToFile("Output\\" + index.ToString()+".xml");
                                //    foreach (var gifPart in pers.GifParts)
                                //    {
                                //        if (gifPart.Data!=null && GifPart.IsGifPart(gifPart.Data))
                                //        {
                                //            WriteToFile($"Output\\{index}-{gifPart.Name}.gif", gifPart.Data);
                                //        }
                                //    }
                                //    //WriteToFile("Output\\" + index.ToString() + ".gif",pers.Data);
                                //}
                                //WriteToFile(Path.Combine("Output", index.ToString()),br.ReadBytes((int) priBlob.Length));
                            }
                        }
                        br.BaseStream.Seek(currentPos, SeekOrigin.Begin);
                        break;
                    default:
                        br.ReadBytes((int)toc.size - 16);
                        break;
                }
            }

            return priRaw;
        }


        private static PriRaw.PriTocEntry GetTocEntryType(PriRaw.PriTocEntry toc)
        {
            if (toc.type.Contains("[mrm_decn_info]"))
            {
                toc.EntryType = PriRaw.TocEntryType.MrmDecnInfo;
            }
            else if (toc.type.Contains("[mrm_pridescex]"))
            {
                toc.EntryType = PriRaw.TocEntryType.MrmPridescex;
            }
            else if (toc.type.Contains("[mrm_hschemaex]"))
            {
                toc.EntryType = PriRaw.TocEntryType.MrmHschemaex;
            }
            else if (toc.type.Contains("[mrm_res_map2_]"))
            {
                toc.EntryType = PriRaw.TocEntryType.MrmResMap2;
            }
            else if (toc.type.Contains("[mrm_dataitem]"))
            {
                toc.EntryType = PriRaw.TocEntryType.MrmDataItem;
            }
            else
            {
                toc.EntryType = PriRaw.TocEntryType.Unknown;
            }
            return toc;
        }

        private static PriRaw.PriTocEntry InstantiateTocEntry(string toc)
        {
            if (toc.Contains("[mrm_decn_info]"))
            {
                return new PriRaw.DecnInfoEntry();
            }
            if (toc.Contains("[mrm_pridescex]"))
            {
                return new PriRaw.PridescexEntry();
            }
            if (toc.Contains("[mrm_hschemaex]"))
            {
                return new PriRaw.HschemaexEntry();
            }
            if (toc.Contains("[mrm_res_map2_]"))
            {
                return new PriRaw.ResMap2Entry();
            }
            if (toc.Contains("[mrm_dataitem]"))
            {
                return new PriRaw.DataItemEntry();
            }
            return new PriRaw.PriTocEntry();
        }

        //This cames from Windows-10-Login-Background-Changer. They did a talent work.
        public static void ModifyLogonGen2(string currentPri, string outputPri, string image)
        {
            var inputStream = File.OpenRead(currentPri);
            var outputStream = File.Create(outputPri);
            var replacementStream = File.OpenRead(image);

            var inputReader = new BinaryReader(inputStream);
            var outputWriter = new BinaryWriter(outputStream);

            inputStream.CopyTo(outputStream);

            var replacementLengthAligned = (Math.Ceiling((double)replacementStream.Length / 8) * 8);

            // header
            inputStream.Seek(0x14, SeekOrigin.Begin);//20
            var headerLength = inputReader.ReadUInt32();
            inputStream.Seek(0xB8, SeekOrigin.Begin);//184
            var dataitemOffset = inputReader.ReadUInt32();
            var origDataitemLength = inputReader.ReadUInt32();
            var dataitemLength = origDataitemLength + replacementLengthAligned;
            outputStream.Seek(0xBC, SeekOrigin.Begin);//188
            outputWriter.Write((int)dataitemLength);

            // dataitem
            outputStream.Seek(headerLength + dataitemOffset + 0x18, SeekOrigin.Begin);//24
            outputWriter.Write((int)dataitemLength); //dataitem标签下一行过8字节是dataitem的总长度
            inputStream.Seek(headerLength + dataitemOffset + 0x24, SeekOrigin.Begin);//36
            var stringCount = inputReader.ReadUInt16();//再下一行过4字节，16位的字符串数量
            var blobCount = inputReader.ReadUInt16();//16位的blob数量
            var origDataLength = inputReader.ReadUInt32();//32位的数据长
            outputStream.Seek(0xC, SeekOrigin.Current);//12
            outputWriter.Write((int)(origDataLength + replacementLengthAligned));
            outputStream.Seek(stringCount * 4, SeekOrigin.Current);
            var blobTableOffset = outputStream.Position;
            var dataOffset = blobTableOffset + blobCount * 8;
            var wallpaperBlobs = new List<int>();
            for (var i = 0; i < blobCount; i++)
            {
                inputStream.Seek(blobTableOffset + i * 8, SeekOrigin.Begin);
                var offset = inputReader.ReadUInt32();
                var length = inputReader.ReadUInt32();
                inputStream.Seek(dataOffset + offset, SeekOrigin.Begin);
                if (inputReader.ReadUInt16() == 0xD8FF) //JPG file header
                {
                    var tr = dataOffset + offset;
                    wallpaperBlobs.Add(i); //only add jpg files id
                }
            }
            if (wallpaperBlobs.Count != 10)
            {
                throw new Exception("Not compatible with this PRI file.");
            }
            foreach (var id in wallpaperBlobs)
            {
                outputStream.Seek(blobTableOffset + id * 8, SeekOrigin.Begin);//update length only for those jpg files
                outputWriter.Write(origDataLength);
                outputWriter.Write((int)replacementStream.Length);
            }

            //data
            outputStream.Seek(dataOffset + origDataLength, SeekOrigin.Begin);
            if (outputStream.Length - outputStream.Position != 0x18)
            {
                throw new Exception("Not compatible with this PRI file.");
            }
            replacementStream.CopyTo(outputStream);

            // footer
            outputStream.Seek((long)(replacementLengthAligned - replacementStream.Length), SeekOrigin.Current);
            outputWriter.Write(0xDEF5FADE);//TOC END SIGN , every toc ends with this and length
            outputWriter.Write((int)dataitemLength);//length
            outputWriter.Write(0xDEFFFADE);
            outputWriter.Write(0x00000000);
            outputWriter.Write("mrm_pri2".ToCharArray());

            outputStream.Seek(0xC, SeekOrigin.Begin);//total length
            outputWriter.Write((int)outputStream.Length);
            outputStream.Seek(-0xC, SeekOrigin.End);
            outputWriter.Write((int)outputStream.Length);//total length

            inputReader.Close();
            outputWriter.Close();
            replacementStream.Close();
        }
    }
}
